/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;

namespace Xceed.Silverlight.ListBox
{
  internal class AddingItemTransitionAnimation : TransitionAnimation
  {
    internal AddingItemTransitionAnimation( ListBoxAnimationSettings animationSettings )
      : base( animationSettings.GetAddingItemDuration() )
    {
      PowerEase opacityEasing = new PowerEase();
      opacityEasing.EasingMode = EasingMode.EaseIn;
      opacityEasing.Power = 9;

      PowerEase itemsAddedEasing = new PowerEase();
      itemsAddedEasing.EasingMode = EasingMode.EaseIn;
      itemsAddedEasing.Power = 6;

      PowerEase otherItemsEasing = new PowerEase();
      otherItemsEasing.EasingMode = EasingMode.EaseInOut;
      otherItemsEasing.Power = 6;

      m_opacityEasing = opacityEasing;
      m_addedItemsMovementEasing = itemsAddedEasing;
      m_otherItemsMovementEasing = otherItemsEasing;
    }

    internal Orientation Orientation
    {
      get;
      set;
    }

    internal override void AddContainer( ListBoxContainer container)
    {
      //Nothing to do
    }

    internal override LayoutChangeInfo CreateLayoutChangeInfo()
    {
      return new LayoutChangeInfo();
    }

    //This method, called within the DetermineLayoutChanges allows derived class to see the big picture before deciding how
    //to behave with individual listBoxContainer elements.
    internal override void DetermineLayoutChangesCore( Dictionary<ListBoxContainer, LayoutChangeInfo> affectedElements )
    {

      IEnumerable<ListBoxContainer> addedElements = from kvp in affectedElements
                                                   let layoutChangeInfo = kvp.Value
                                                   let addedContainer = kvp.Key
                                                   where layoutChangeInfo.ChangeType == ChangeType.ElementAdded
                                                   select addedContainer;
      // No Element Added
      if( addedElements.Count() <= 0 )
      {
        m_translation = new Point( double.NaN, double.NaN );
      }
      else
      {
        // At least on element 
        Rect addedOperationRect = this.ComputeOperationsRect( addedElements );

        if( this.Orientation == Orientation.Vertical )
        {
          m_translation.X = double.NaN;
          m_translation.Y = addedOperationRect.Height;
        }
        else
        {
          m_translation.X = addedOperationRect.Width;
          m_translation.Y = double.NaN;
        }
      }

    }

    //This method, called within the PreapreContainerCore method, automatically invoked on each "changed" ListBoxContainer detected
    //during the StartTransitionAnimationMethod.
    internal override bool PrepareContainerCore( ListBoxContainer container, LayoutChangeInfo changeInfo )
    {
      switch( changeInfo.ChangeType )
      {
        case ChangeType.ElementAdded:
          this.PrepareAddedElement( container, changeInfo );
          break;

        case ChangeType.ElementRemoved:
          this.PrepareRemovedElement( container, changeInfo );
          break;

        //If element is changed, don't change a thing.
        case ChangeType.ElementChanged:
          this.PrepareChangedElement( container, changeInfo );
          break;
        default:
          break;
      }

      //systematically return true. All elements flagged as changed are handled in this transition.
      return true;

    }

    //This method, called within the OnRendering method, allows derived classes to update the LayoutChangeInfo associated with ListBoxContainer and
    //also allow for "custom" animations and actions to be performed on the container.
    internal override bool UpdateContainerCore( double normalizedElapsedTime, ListBoxContainer container )
    {

      if( container == null )
        throw new ListBoxInternalException();

      container.UpdateAnimations( normalizedElapsedTime );

      //There is no custom animations in this transition, only those handled by the EasingFunctions.
      //Since I have no custom timeline, I do not have to decide when its "finished". Returning false
      //will just make it so that the animation will proceed for every frame of every item.
      return false;
    }

    //This method, called within the CleanContainer method, allows derived classes to perform any relevant cleanup operation associated with
    //a ListBoxContainer animation. It is there to "compensate" for any action undertaken in the prepare and update abstract methods.
    internal override void CleanContainerCore( ListBoxContainer container )
    {
      if( container == null )
        throw new ListBoxInternalException();

    }

    private void PrepareChangedElement( ListBoxContainer container, LayoutChangeInfo changeInfo )
    {
      //Removed items in the case of a ItemsAddedTransitionAnimation are items that are shifted out of view
      //by the insertion of new items.

      //Animation for these items is to push them away by the amount of inserted items.
      PositionAnimation positionAnimation = new PositionAnimation();
      RotateAnimation rotatedAnimation = new RotateAnimation();
      ScaleAnimation scaleAnimation = new ScaleAnimation();
      ( ( IAnimation )positionAnimation ).ParentTransition = this;
      ( ( IAnimation )positionAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )rotatedAnimation ).ParentTransition = this;
      ( ( IAnimation )rotatedAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )scaleAnimation ).ParentTransition = this;
      ( ( IAnimation )scaleAnimation ).EasingFunction = m_otherItemsMovementEasing;

      positionAnimation.XChange = changeInfo.XChange;
      positionAnimation.YChange = changeInfo.YChange;
      rotatedAnimation.AngleChange = changeInfo.AngleChange;
      scaleAnimation.ScaleChange = changeInfo.ScaleChange;


      container.AddAnimation( positionAnimation );
      container.AddAnimation( rotatedAnimation );
      container.AddAnimation( scaleAnimation );
    }

    private void PrepareAddedElement( ListBoxContainer container, LayoutChangeInfo changeInfo )
    {

      if( changeInfo == null )
        throw new ListBoxInternalException();

      //In the case of an ItemsAddedTransitionAnimation, added elements are those inserted in the layout.

      //In that case, this class will animate them from the insertion point to their respective position

      Point elementActualPosition = container.ActualComputedPosition;
      OpacityAnimation opacityAnimation = new OpacityAnimation();

      ( ( IAnimation )opacityAnimation ).EasingFunction = m_opacityEasing;
      ( ( IAnimation )opacityAnimation ).ParentTransition = this;
      ( ( IAnimation )opacityAnimation ).AnimationDirection = AnimationDirection.ToReferencePosition;

      opacityAnimation.OpacityChange = -changeInfo.OpacityChange;

      container.AddAnimation( opacityAnimation );

    }

    private void PrepareRemovedElement( ListBoxContainer container, LayoutChangeInfo changeInfo )
    {
      //Removed items in the case of a ItemsAddedTransitionAnimation are items that are shifted out of view
      //by the insertion of new items.

      //Animation for these items is to push them away by the amount of inserted items.
      Point elementActualPosition = container.ActualComputedPosition;
      SizeAnimation sizeAnimation = new SizeAnimation();
      OpacityAnimation opacityAnimation = new OpacityAnimation();
      RotateAnimation rotateAnimation = new RotateAnimation();
      PositionAnimation positionAnimation = new PositionAnimation();

      ( ( IAnimation )sizeAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )sizeAnimation ).ParentTransition = this;

      ( ( IAnimation )opacityAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )opacityAnimation ).ParentTransition = this;
      ( ( IAnimation )opacityAnimation ).AnimationDirection = AnimationDirection.FromReferencePosition;

      ( ( IAnimation )rotateAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )rotateAnimation ).ParentTransition = this;

      ( ( IAnimation )positionAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )positionAnimation ).ParentTransition = this;
      ( ( IAnimation )positionAnimation ).AnimationDirection = AnimationDirection.FromReferencePosition;


      positionAnimation.YChange = m_translation.Y;
      positionAnimation.XChange = m_translation.X;

      sizeAnimation.MaximumHeightChange = m_translation.Y;
      sizeAnimation.MaximumWidthChange = m_translation.X;

      rotateAnimation.AngleChange = changeInfo.AngleChange;
      opacityAnimation.OpacityChange = changeInfo.OpacityChange;

      container.AddAnimation( rotateAnimation );
      container.AddAnimation( opacityAnimation );
      container.AddAnimation( sizeAnimation );
      container.AddAnimation( positionAnimation );
    }

    #region Private Methods
    private Rect ComputeOperationsRect( IEnumerable<ListBoxContainer> elements )
    {
      Rect operationRect = new Rect( double.NaN, double.NaN, double.NaN, double.NaN );
      bool operationOccured = false;
      double lowestOperationX = double.MaxValue;
      double lowestOperationY = double.MaxValue;
      double highestOperationX = double.MinValue;
      double highestOperationY = double.MinValue;

      foreach( ListBoxContainer listBoxContainer in elements )
      {
        Point containerPosition = listBoxContainer.AbsoluteOffset;

        if( ( containerPosition.X != double.NaN ) && ( containerPosition.Y != double.NaN ) )
          continue;

        double elementHeight = listBoxContainer.AbsoluteElementHeight;
        double elementWidth = listBoxContainer.AbsoluteElementWidth;

        if( containerPosition.X < lowestOperationX )
        {
          lowestOperationX = containerPosition.X;
        }

        if( containerPosition.Y < lowestOperationY )
        {
          lowestOperationY = containerPosition.Y;
        }

        if( ( containerPosition.X + elementWidth ) > highestOperationX )
        {
          highestOperationX = containerPosition.X + elementWidth;
        }

        if( ( containerPosition.Y + elementHeight ) > highestOperationY )
        {
          highestOperationY = containerPosition.Y + elementHeight;
        }

        operationOccured = true;
      }

      if( operationOccured )
      {
        operationRect.X = lowestOperationX;
        operationRect.Y = lowestOperationY;
        operationRect.Width = highestOperationX - lowestOperationX;
        operationRect.Height = highestOperationY - lowestOperationY;
      }

      return operationRect;
    }

    #endregion


    #region Private Members

    private IEasingFunction m_opacityEasing;
    private IEasingFunction m_addedItemsMovementEasing;
    private IEasingFunction m_otherItemsMovementEasing;
    private Point m_translation = new Point( 0.0d, 0.0d );

    #endregion
  }
}
